package com.mapforge.map.tools;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.UndoableEdit;

import com.mapforge.listener.RenderEvent;
import com.mapforge.listener.RenderListener;
import com.mapforge.map.TileMap;

/**
 * The {@link AbstractMapTool} class provides a basic implementation of the
 * {@link MapToolInterface} interface. All functions are defined except
 * {@link MapToolInterface#performEdit()}. Final behavior is dependent on
 * implementation.
 * 
 * @see MapToolInterface
 * @author Will Morrison
 * @version 1.0
 * @since 0.3
 */
public abstract class AbstractMapTool implements MapToolInterface {

	/**
	 * Point the mouse is currently at in tile coordinates. Only useful during a
	 * drag.
	 */
	private Point currentPoint;

	/**
	 * Point the mouse was first pressed at in tile coordinates.
	 */
	private Point startPoint;

	/**
	 * Very short description of this tool's behavior.
	 */
	private String description;

	/**
	 * The map this tool is editing.
	 */
	private TileMap tileMap;

	/**
	 * Listeners interested in edits this tool is performing.
	 */
	private List<UndoableEditListener> undoListeners;

	/**
	 * Listeners interested in edits affecting their graphical representation.
	 */
	private List<RenderListener> renderListeners;

	/**
	 * Indicates whether this {@link AbstractMapTool} is performing edits.
	 */
	private boolean isEditing;

	public AbstractMapTool(String description) {
		this.description = description;
		this.startPoint = new Point();
		this.currentPoint = new Point();
		this.undoListeners = new ArrayList<UndoableEditListener>();
		this.renderListeners = new ArrayList<RenderListener>();
	}

	protected void fireRenderListenerEvent(RenderEvent event) {
		for (RenderListener listener : this.renderListeners) {
			listener.renderEventOccurred(event);
		}
	}

	public boolean addRenderListener(RenderListener listener) {
		return this.renderListeners.add(listener);
	}

	public boolean removeRenderListener(RenderListener listener) {
		return this.renderListeners.remove(listener);
	}

	@Override
	public void setAnchorPoint(Point startPoint) {
		this.startPoint = startPoint;

	}

	@Override
	public Point getAnchorPoint() {
		return startPoint;
	}

	@Override
	public void setMovingPoint(Point currentPoint) {
		this.currentPoint = currentPoint;
	}

	@Override
	public Point getMovingPoint() {
		return currentPoint;
	}

	@Override
	public TileMap getTileMap() {
		return tileMap;
	}

	@Override
	public void setTileMap(TileMap tileMap) {
		this.tileMap = tileMap;
	}

	@Override
	public String getDescription() {
		return description;
	}

	/**
	 * Listeners interested in edits this tool performs should register
	 * themselves through this function.
	 * 
	 * @param undoListener
	 *            listener concerned with edits this tool performs
	 */
	public void addUndoableEditListener(UndoableEditListener undoListener) {
		undoListeners.add(undoListener);
	}

	/**
	 * Listeners which are no longer interested in edits this tool performs and
	 * unregister themselves through this function. This method will return true
	 * if the listener was previously registered with this tool.
	 * 
	 * @param listener
	 *            listener no longer concerned with this tool's edits
	 * @return whether this tool was actually registered with this tool prior to
	 *         calling this method
	 */
	public boolean removeUndoableEditListener(UndoableEditListener listener) {
		return undoListeners.remove(listener);
	}

	/**
	 * Removes all listeners interested in edits this tool performs.
	 */
	public void clearUndoableEditListeners() {
		undoListeners.clear();
	}

	/**
	 * Informs all registered listeners that this tool has performed the
	 * specified {@link UndoableEdit}
	 * 
	 * @param undoRedo
	 *            specified {@link UndoableEdit} performed by this tool
	 */
	protected void fireEditActionPerformed(UndoableEdit undoRedo) {
		for (int i = 0; i < undoListeners.size(); i++) {
			undoListeners.get(i).undoableEditHappened(
					new UndoableEditEvent(this, undoRedo));
		}
	}

	@Override
	public void beginEdit() {
		this.isEditing = true;
	}

	/**
	 * Returns whether this tool is in the middle of performing an edit.
	 * 
	 * @return
	 */
	public boolean isEditing() {
		return this.isEditing;
	}

	@Override
	public void endEdit() {
		this.isEditing = false;
	}

}
